#ifndef SOS_PAGE_TABLE_H
#define SOS_PAGE_TABLE_H

#include <cspace/cspace.h>
#include <sel4/sel4.h>

#include "process.h"

#define SOS_PAGE_TABLE_ERROR    (-1) /* Mysterious fatal event */
#define SOS_PAGE_TABLE_SUCCESS  (0)  /* OK */
#define SOS_PAGE_TABLE_OOM      (1)  /* Out of memory - retry with scratch */
#define SOS_PAGE_TABLE_PERM     (2)  /* Permissions mismatch */
#define SOS_PAGE_TABLE_INVAL    (3)  /* Invalid arguments */
#define SOS_PAGE_TABLE_SEGFAULT (4)  /* Process cannot continue */
#define SOS_PAGE_TABLE_FAULT    (5)  /* A fault would be required to proceed */
#define SOS_PAGE_TABLE_ABORT    (6)  /* Process has been killed, abort all ops */

/*
 *  PD/PT split: 10 / 10.
 */

/*
 * PTE split: most significant  8 bits = Bookkeeping/status/permission
 *            remaining        24 bits = frametable/swaptable index.
 */

typedef struct page_attrs {
    unsigned char swapped : 1;    /* Evicted to disk; framenumber = swap idx */
    unsigned char pinned : 1;     /* Mapped in and unswappable */
    unsigned char readable : 1;   /* Process can read from page */
    unsigned char writeable : 1;  /* Process can write to page */
    unsigned char executable : 1; /* Process can execute page contents */
} page_attrs;

/* Individual entries in the page table must be one 32-bit word */
/* (A sanity check on these sizes takes place at PD creation)   */
typedef struct page_table_entry {
    struct page_attrs attrs;       /* 8 bits at most */
    unsigned int framenumber : 24; /* 24 bits at most */
    /* Frame numbers are overloaded.                                        */
    /* If attrs.swapped = 0, it's a frame number; else, it's a swap number. */
} page_table_entry;

typedef page_table_entry *sos_page_table;
typedef sos_page_table *sos_page_directory;

/* Common forms of VM attributes */
extern const page_attrs page_rw;
extern const page_attrs page_rwx;
extern const page_attrs page_guard;

/**
 * This is the type of callback used for most asynchronous page table
 * operations.
 * @param[in] err The error code from the completed operation.
 * @param[in] data An opaque pointer to data the caller
 *                 can use to disambiguate.
 */
typedef void (*page_table_cb)(int err, void *cookie);

/**
 * The type of callback used by page_pin. It passes the SOS window address
 * of the backing frame to the caller, to avoid potential races.
 */
typedef void (*page_pin_cb)(int err, seL4_Word sos_vaddr, void *cookie);

/**
 * Create a pager / VM subsystem for the new process at pcb.
 * This is an asynchronous operation - we need a frame to proceed.
 * @pre Page directory does not exist in pcb
 * @param pcb  Pointer to the process's PCB
 * @param cb   Callback to be run when the operation is complete.
 *             cb will be run even if the operation fails.
 * @param cb_data Data that will be returned untouched to cb.
 * @return SOS_PAGE_TABLE_SUCCESS, or SOS_PAGE_TABLE_OOM if out of memory
 */
int sos_pager_create(sos_pcb *pcb, page_table_cb cb, void *cookie);

/**
 * Destroy the pager for a process, freeing all resources.
 * @pre Process has been initialised correctly, sos_pager_create has been run.
 * @param pcb Pointer to the process's PCB.
 * @return SOS_PAGE_TABLE_SUCCESS, or nonzero if catastrophe occurs
 */
int sos_pager_destroy(sos_pcb *pcb);

/**
 * Map a physical frame in for proc_vaddr, then call cb.
 */
int sos_page_fault_handle(sos_pcb *pcb, seL4_Word proc_vaddr,
                          page_table_cb cb, void *cookie);

/**
 * Pin an unswappable physical frame for some process vaddr.
 *
 * When cb is invoked with success, a frame will be backing proc_vaddr,
 * and it will not be swappable until release is called.
 *
 * Pinned frames will not be free'd by the pager, to avoid race conditions.
 * The caller should anticipate potential process_delete, and manually
 * free the frame when the resource is finalised.
 *
 * This is an asynchronous operation - we need a frame to proceed.
 */
int sos_page_pin(sos_pcb *pcb, seL4_Word proc_vaddr,
                 page_pin_cb cb, void *cookie);

/**
 * The page containing proc_vaddr will be swappable again.
 * This is a synchronous operation.
 */
int sos_page_release(sos_pcb *pcb, seL4_Word proc_vaddr);

/**
 * Given a capability from the frame table, map it in to process address space
 * at proc_vaddr using seL4 facilities.
 * This is a synchronous operation. The PTE must exist, i.e. page_walk first.
 * @pre The page_table_entry corresponding to proc_vaddr exists, i.e. the
 *      page table has been walked, or the frame is being swapped.
 */
int sos_page_map(sos_pcb *pcb, seL4_Word proc_vaddr, seL4_Word proc_cap);

/**
 * Undo the seL4-level mapping for proc_vaddr. This does not free the frame.
 * If proc_vaddr is later accessed, the frame table will call sos_page_map.
 * This is a synchronous operation.
 */
int sos_page_unmap(sos_pcb *pcb, seL4_Word proc_vaddr, seL4_Word proc_cap);

/**
 * The frame corresponding to proc_vaddr has been fetched from swap. The new
 * frame number is frame_num. This updates the PTE with that frame_num.
 * @pre The frame has been swapped out before. The PTE must exist.
 * @param proc_vaddr the process-virtual address the frame corresponds to.
 */
int sos_page_swapin(sos_pcb *pcb, seL4_Word proc_vaddr, seL4_Word frame_num);

/**
 * The frame backing proc_vaddr has been swapped out. Update tables accordingly.
 * The page was already unmapped.
 * This is a synchronous operation.
 */
int sos_page_swapout(sos_pcb *pcb, seL4_Word proc_vaddr, seL4_Word swap_num);

/**
 * Look up the kernel address for proc_vaddr, without causing a page fault.
 * Combine with pin / release for predictable behaviour.
 */
int sos_page_lookup_nofault(sos_pcb *pcb, seL4_Word proc_vaddr,
                            seL4_Word *sos_vaddr, page_attrs *attrs);

/**
 * Copy up to a frame from src into process memory, without triggering any
 * page faults. Combine with pin / release for predictable behaviour.
 */
int sos_copyin_nofault(sos_pcb *pcb, seL4_Word proc_vaddr, seL4_Word src,
                       size_t nbyte, size_t *nsent);

/**
 * Copy up to a frame into dst from process memory, without triggering any
 * page faults. Combine with pin / release for predictable behaviour.
 */
int sos_copyout_nofault(sos_pcb *pcb, seL4_Word proc_vaddr, seL4_Word dst,
                        size_t nbyte, size_t *nsent);

/**
 * Create a new VM region at base spanning npages with attributes attrs.
 * This is a synchronous operation.
 */
int sos_create_region(sos_pcb *pcb, seL4_Word base, seL4_Word npages,
                      page_attrs attrs);

/**
 * Move the brk. Synchronous operation.
 */
int sos_brk_move(sos_pcb *pcb, seL4_Word new_brk_vaddr);

/**
 * Flush the instruction cache for the frame currently backing
 * proc_vaddr.
 *
 * @pre proc_vaddr is pinned or otherwise mapped to a physical frame
 */
int sos_page_flush_icache(sos_pcb *pcb, seL4_Word proc_vaddr);

#endif // SOS_PAGE_TABLE_H
